package com.grupit.module.user.web.gwt.client.pluggablefeatures.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;

import com.google.gwt.core.client.Scheduler;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.Request;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.google.web.bindery.requestfactory.shared.ServerFailure;
import com.grupit.engine.util.FormatterLogger;

/*
 * TODO: Estudiar si es sustituible por RequestBactcher de GWT
 */
public class RequestQueue {

	private static FormatterLogger logger = FormatterLogger.getLogger(RequestQueue.class);

	private List<ContextReceiver<?>> queue = new ArrayList<ContextReceiver<?>>();
	private boolean requestInProgress = false;
	private boolean cancelNextRequestsOnFail;

	public boolean hasPendingRequests() {
		return !queue.isEmpty();
	}

	private static class ContextReceiver<T> {
		public RequestContext context;
		public Receiver<Void> contextReceiver;
		public Request<? extends T> request;
		public Receiver<T> requestReceiver;
	}

	public RequestQueue() {
		// Nada
	}

	public RequestQueue(boolean cancelNextRequestsOnFail) {
		this.setCancelNextRequestsOnFail(cancelNextRequestsOnFail);
	}

	/*
	 * FIXME: Si lanzamos un RequestContext en lugar de un Request, al menos en
	 * GWT 2.4, la excepción no se captura bien porque no asocia correctamente
	 * el receiver (si depuras el código dice que viene a null). Con los Request
	 * funciona correctamente así que mientras estemos en la versión 2.4. lo
	 * dejaremos sólo con Request.
	 */
	public void fireOrQueue(RequestContext context) {
		logger.warn("¡¡¡¡FIRE OR QUEUE EN PRUEBAS!!!! SI FALLA ALGO EL ORIGEN PODRÍA ESTAR AQUÍ");
		fireOrQueue(context, null);
	}

	public <T> void fireOrQueue(RequestContext context, final Receiver<Void> receiver) {
		logger.warn("¡¡¡¡FIRE OR QUEUE EN PRUEBAS!!!! SI FALLA ALGO EL ORIGEN PODRÍA ESTAR AQUÍ");
		if (!requestInProgress) {
			requestInProgress = true;
			context.fire(new Receiver<Void>() {

				@Override
				public void onSuccess(Void response) {
					if (receiver != null) {
						receiver.onSuccess(response);
					}

					ContextReceiver<?> nextRequest = (!queue.isEmpty() ? queue.remove(0) : null);
					requestInProgress = false;
					if (nextRequest != null) {
						fireOrQueue(nextRequest);
					}
				}

				@Override
				public void onConstraintViolation(Set<ConstraintViolation<?>> violations) {
					if (receiver != null) {
						receiver.onConstraintViolation(violations);
					} else {
						super.onConstraintViolation(violations);
					}
					requestInProgress = false;
				}

				@Override
				public void onFailure(ServerFailure error) {
					if (receiver != null) {
						receiver.onFailure(error);
					} else {
						super.onFailure(error);
					}
					requestInProgress = false;
				}
			});
		} else {
			ContextReceiver<T> contextReceiver = new ContextReceiver<T>();
			contextReceiver.context = context;
			contextReceiver.contextReceiver = receiver;
			queue.add(contextReceiver);
		}
	}

	public <T> void fireOrQueue(Request<? extends T> request) {
		fireOrQueue(request, null);
	}

	public <T> void fireOrQueue(Request<? extends T> request, final Receiver<T> receiver) {
		if (!requestInProgress) {
			requestInProgress = true;
			request.fire(new Receiver<T>() {

				@Override
				public void onSuccess(T response) {
					if (receiver != null) {
						receiver.onSuccess(response);
					}

					requestInProgress = false;
					fireNext();
				}

				@Override
				public void onConstraintViolation(Set<ConstraintViolation<?>> violations) {
					if (receiver != null) {
						receiver.onConstraintViolation(violations);
					} else {
						super.onConstraintViolation(violations);
					}
					requestInProgress = false;
					if (cancelNextRequestsOnFail) {
						queue.clear();
					} else {
						fireNext();
					}
				}

				@Override
				public void onFailure(ServerFailure error) {
					if (receiver != null) {
						receiver.onFailure(error);
					} else {
						super.onFailure(error);
					}
					requestInProgress = false;
					if (cancelNextRequestsOnFail) {
						queue.clear();
					} else {
						fireNext();
					}
				}
			});
		} else {
			ContextReceiver<T> contextReceiver = new ContextReceiver<T>();
			contextReceiver.request = request;
			contextReceiver.requestReceiver = receiver;
			queue.add(contextReceiver);
		}
	}

	protected <T> void fireOrQueue(final ContextReceiver<T> nextRequest) {
		Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {

			@Override
			public void execute() {
				if (nextRequest.context != null) {
					fireOrQueue(nextRequest.context, nextRequest.contextReceiver);
				} else {
					fireOrQueue(nextRequest.request, nextRequest.requestReceiver);
				}
			}
		});
	}

	public boolean isCancelNextRequestsOnFail() {
		return cancelNextRequestsOnFail;
	}

	public void setCancelNextRequestsOnFail(boolean cancelNextRequestsOnFail) {
		this.cancelNextRequestsOnFail = cancelNextRequestsOnFail;
	}

	protected void fireNext() {
		ContextReceiver<?> nextRequest = (!queue.isEmpty() ? queue.remove(0) : null);
		if (nextRequest != null) {
			fireOrQueue(nextRequest);
		}
	}

}
